<!--
tinymce 富文本编辑器
@author zqy
@created 2021/12/26 10:56
-->
<!--<template>
  <div>
    <div v-if="editorConfig.inline" :id="editorId" :key="tinymceFlag" />
    <textarea v-else :id="editorId" :key="tinymceFlag" />
  </div>
</template>-->

<script>
import Scriptjs from 'scriptjs'
import request from '@/utils/request'
import { formatURL } from './utils'
import { v4 as uuidv4 } from 'uuid'
export default {
  name: 'QUPTinyEditor',
  model: {
    prop: 'content',
    event: 'change'
  },
  props: {
    content: {
      type: String,
      default: ''
    }, // 富文本编辑器显示的内容
    baseUrl: {
      type: String,
      default: ''
    }, // 应用目录
    editorId: {
      type: String,
      default: `tiny-mce-editor-${uuidv4()}`
    }, // 编辑器id, 多个编辑器时使用
    editorConfig: {
      type: Object,
      default() {
        return {}
      }
    }, // 编辑器初始化配置参数对象
    mode: {
      type: String,
      default: 'design'
    }, // 编辑器模式，readonly(只读)/design(编辑)
    placeholder: {
      type: String,
      default: '请输入'
    },
    uploadData: {
      type: Object,
      default: () => {
        return {
          // scene: 'prodDetail',
          // source: 'TinyMCE Editor'
        }
      }
    }, // 上传时附带的额外参数,富文本中上传图片到图片库时额外附带的参数
    imageAccept: {
      type: Array,
      default: () => {
        return ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
      }
    }, // 允许上传的图片类型
    videoAccept: {
      type: Array,
      default: () => {
        return ['.mp3', '.mp4', '.ogv']
      }
    }, // 允许上传的视频类型
    fileAccept: {
      type: Array,
      default: () => {
        return ['.zip', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
      }
    }, // 允许上传的文件类型

    filePrefix: {
      type: String,
      default: ''
    }, // 上传文件前缀
    imageSize: {
      type: Number,
      default: 2// 默认2M
    }, // 上传图片的大小限制
    beforeUpload: {
      type: Function,
      default: () => {
        return true
      }
    }, // 上传前的处理
    uploadResField: {
      type: String,
      default: 'file'
    }, // 上传返回字段
    uploadUrl: {
      type: String,
      default: '/api/v1/file/upload'
    }, // 上传url地址
    customButtons: {
      type: Array,
      default: () => [] // [{code,name,text,tooltip,onActive}]
    } // 自定义按钮
  },
  data() {
    return {
      editor: null,
      tinymceFlag: 1,
      dialogPicUpload: false,
      hasInit: false,
      hasChange: false
    }
  },
  watch: {
    mode: {
      handler(val) {
        this.setMode(val)
      }
    }
  },
  activated() {
    this.tinymceFlag++
  },
  mounted() {
    // window.vm = this
    this.initEidtor()
  },
  beforeDestroy() {
    if (this.editor) {
      this.editor.destroy()
    }
  },
  methods: {
    /**
     * 初始化编辑器
     */
    initEidtor() {
      Scriptjs(this.baseUrl + '/cdn/tinymce/tinymce.min.js',
        () => {
          let defaultToolBar = 'undo redo removeformat | cut copy paste | fontsizeselect forecolor backcolor | bold italic underline strikethrough | alignleft alignright aligncenter alignjustify alignnone | bullist numlist  | outdent indent blockquote  table | link unlink hr emoticons | image axupimgs media |  code  codesample  fullscreen preview '
          // let defaultToolBar = 'undo redo removeformat | cut copy paste | fontsizeselect forecolor backcolor | bold italic underline strikethrough | alignleft alignright aligncenter alignjustify alignnone | bullist numlist  | outdent indent blockquote  codesample  table | link unlink hr | image axupimgs media | emoticons  code  fullscreen preview '
          const customToolBar = this.customButtons.map(v => {
            return v.code
          })
          if (customToolBar.length) {
            defaultToolBar = `${defaultToolBar} | ${customToolBar.join(' ')}`
          }

          const defaultConfig = {
            selector: '#' + this.editorId,
            height: 500,
            language: 'zh_CN',
            menubar: true, // 设置富文本编辑器菜单, 默认true
            // menubar: 'file edit insert view format table tools help', // （1级菜单）最上方的菜单
            branding: false, // 是否关闭底部官网提示（右下角技术支持）水印“Powered by TinyMCE” , 默认true
            statusbar: true, // 显示底部状态栏 默认true

            readonly: false, // 设置只读属性 默认 false
            resize: false, // 调节编辑器大小 默认 true

            autosave_ask_before_unload: false, // 当关闭或跳转URL时，弹出提示框提醒用户仍未保存变更内容。 默认 true  需引入插件autosave
            autosave_interval: '3s', // 设置自动保存为草稿时间 单位只能为s 需引入插件autosave
            autosave_prefix: `editor_${this.$route.path}`, // 设置自动保存为草稿时前缀 本地localStorage中存储  需引入插件autosave
            autosave_retention: '300m', // 自动草稿的有效期 单位只能为m  需引入插件autosave

            browser_spellcheck: true, // 拼写检查
            elementpath: true, // 是否显示编辑器底部左侧元素路径

            image_advtab: true, // 为上传图片窗口添加高级属性 需引入插件image
            paste_data_images: true, // 粘贴data格式的图像 需引入插件paste 谷歌浏览器无法粘贴
            paste_as_text: true, // 默认粘贴为文本 需引入插件paste 谷歌浏览器无法粘贴

            contextmenu: 'copy paste cut link', // 上下文菜单 默认 false
            draggable_modal: true, // 模态框拖动 默认false
            placeholder: this.placeholder, // 占位符
            // theme: 'silver', // 主题 必须引入
            // content_css: `/tinymce/skins/content/default`, // 富文本编辑器内容区域样式
            content_style: 'body, p{font-size: 14px}', // 为内容区编辑自定义css样式
            fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px', // 工具栏自定义字体大小选项
            font_formats: "微软雅黑='微软雅黑'; 宋体='宋体'; 黑体='黑体'; 仿宋='仿宋'; 楷体='楷体'; 隶书='隶书'; 幼圆='幼圆'; 方正舒体='方正舒体'; 方正姚体='方正姚体'; 等线='等线'; 华文彩云='华文彩云'; 华文仿宋='华文仿宋'; 华文行楷='华文行楷'; 华文楷体='华文楷体'; 华文隶书='华文隶书'; Andale Mono=andale mono,times; Arial=arial; Arial Black=arial black;avant garde; Book Antiqua=book antiqua;palatino; Comic Sans MS=comic sans ms; Courier New=courier new;courier; Georgia=georgia; Helvetica=helvetica; Impact=impact;chicago; Symbol=symbol; Tahoma=tahoma;arial; sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms; Verdana=verdana;geneva; Webdings=webdings; Wingdings=wingdings", // 工具栏自定义字体选项
            toolbar_sticky: true, // 粘性工具栏 默认false (在向下滚动网页直到不再可见编辑器时，将工具栏和菜单停靠在屏幕顶部)
            toolbar_mode: 'sliding', // sliding生效条件toolbar必须为字符串,且有'|'区分,不能为数组
            fullscreen_native: true, // 设为true后，全屏将不在是网页全屏，而是使用浏览器的全屏模式（同按F11键）,需引入插件fullscreen
            mediaembed_max_width: 450,
            plugins: 'autosave,advlist,image,imagetools,axupimgs,media,link,table,fullscreen,preview,lists,wordcount,code,codesample,emoticons,hr', // mediaembed(增强媒体嵌入),advcode(高级代码)
            toolbar: defaultToolBar, // | myCustomToolbarButton myCustomMedia submit clearContent
            imagetools_cors_hosts: ['quanyou.com.cn', 'baidu.com'], // 配置图片编辑时，图片来源跨域白名单
            imagetools_proxy: 'proxy.php', // 后端配置参照： http://tinymce.ax-z.cn/plugins/imagetools.php
            imagetools_toolbar: 'rotateleft rotateright | flipv fliph | imageoptions', // 移除编辑 editimage
            media_alt_source: false, // 显示隐藏资源备用地址输入框
            // 初始化前执行
            setup: (editor) => {
              // 监听鼠标按下事件
              editor.on('keydown', (e) => {
                if (e.keyCode === 9) {
                  if (e.shiftKey) {
                    editor.execCommand('Outdent')
                  } else {
                    editor.execCommand('Indent')
                  }
                  e.preventDefault()
                  e.stopPropagation()
                }
              })
              // 注册自定义操作按钮
              this.customButtons.forEach(v => {
                if (v.code) {
                  const btnObj = {
                    text: v.text,
                    onAction: (editor) => {
                      v.onAction && v.onAction(editor)
                    }
                  }
                  if (v.tooltip) {
                    btnObj.tooltip = v.tooltip
                  }
                  editor.ui.registry.addButton(v.code, btnObj)
                }
              })
              /* // 注册自定义上传按钮
              editor.ui.registry.addButton('myCustomToolbarButton', {
                text: '<i class="el-icon-upload" style="font-size: 24px"></i>',
                tooltip: '从图库选择图片',
                onAction: () => {
                  this.openPicStock()
                }
              })*/
            },
            // 设置事件回调函数 ,初始化结束后执行
            init_instance_callback: (editor) => {
              // 里面实现双向数据绑定功能
              if (this.value) {
                editor.setContent(this.value)
              }

              this.hasInit = true
              editor.on('Input undo redo Change execCommand SetContent', (e) => {
                this.hasChange = true
                // editor.getContent({ format: ''text }) // 获取纯文本
                this.$emit('change', editor.getContent())
              })
            },
            // 图片回调url的相对路径，可写可不写，为兼容图片回调而设
            image_prepend_url: `${this.filePrefix}`, // 指定图片URL补齐前缀。仅image使用
            // images_upload_base_path: `${this.filePrefix}`, // 全局使用： 图片回调url的相对路径，可写可不写，为兼容图片回调而设。
            axupimgs_filetype: this.imageAccept.join(','), // 设置允许上传的图片类型
            // 自定义图片上传逻辑
            images_upload_handler: (blobInfo, success, failure) => {
              const file = blobInfo.blob()
              const contentType = file.type // 文件类型：image/png
              const suffix = contentType.split('/')[1] // 文件后缀
              if (!this.imageAccept.includes(`.${suffix}`)) {
                this.$message.warning({
                  message: `上传资源只能是${this.imageAccept.join(',')} 格式!`,
                  duration: 5000,
                  customClass: 'zZindex'
                })
                return false
              }
              this.uploadToServer('image', file, success, failure)
            },

            file_picker_types: 'image media', // 'file image media' 只允许图片和视频自定义上传则去掉file,加上file则链接也可上传
            // 自定义文件上传逻辑
            file_picker_callback: (callback, value, meta) => {
              // const metaFieldname = meta.fieldname // 插件字段名称
              const metaType = meta.filetype // 插件操作类型
              // console.log(meta, value)
              // 文件分类
              let filetype = this.fileAccept.concat(this.videoAccept).join(',')
              // '.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4'
              // 为不同插件指定文件类型
              switch (metaType) {
                case 'image':
                  filetype = this.imageAccept.join(',') // '.jpg, .jpeg, .png, .gif'
                  // filetype = 'image/gif,image/jpeg,image/png,image/jpg'
                  break
                case 'media':
                  filetype = this.videoAccept.join(',') // '.mp3, .mp4,.ogv'
                  // filetype = 'video/mp4,audio/mpeg,video/ogg,video/AVI,video/mov,video/FLV,video/rmvb,video/mtv,video/mpeg'
                  break
                case 'file':
                  filetype = this.fileAccept.join(',')
                  break
                default:
              }
              // 模拟一个input用于添加本地文件
              var input = document.createElement('input')
              input.setAttribute('type', 'file')
              input.setAttribute('accept', filetype)
              input.setAttribute('id', 'inputfile')
              input.click()
              input.onchange = (e) => {
                // const cInput = e.path[0]
                const cInput = e.target
                const file = cInput.files[0] // 文件
                const fileName = file.name
                const contentType = file.type // 文件类型：image/png 、 video/mp4
                const nameDots = fileName.split('.')
                const suffix = nameDots[nameDots.length - 1] // 文件后缀
                console.log(fileName, contentType, suffix, filetype.indexOf(suffix))
                if (filetype.indexOf(suffix) === -1) {
                  this.$message.warning({
                    message: `上传资源只能是${filetype} 格式!`,
                    duration: 5000,
                    customClass: 'zZindex'
                  })
                  return false
                }

                this.uploadToServer(metaType, file, (url) => {
                  // 上传成功
                  url = formatURL(url, this.filePrefix)
                  if (metaType === 'file') {
                    callback(url, { text: file.name })
                  } else if (metaType === 'image') {
                    callback(url, { alt: file.name })
                  } else if (metaType === 'media') {
                    // callback(url, { source2: 'alt.ogg', poster: 'image.jpg' })
                    callback(url, { source2: '', poster: '' })
                  }
                }, (error) => {
                  // 上传失败
                  console.log('Error: ' + error)
                  callback(file, { alt: file.name })
                })

                // 下方被注释掉的是官方的一个例子,供参考
                /* var reader = new FileReader();
                    reader.onload = function () {
                        // Note: Now we need to register the blob in TinyMCEs image blob
                        // registry. In the next release this part hopefully won't be
                        // necessary, as we are looking to handle it internally.
                        var id = 'blobid' + (new Date()).getTime();
                        var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                        var base64 = reader.result.split(',')[1];
                        var blobInfo = blobCache.create(id, file, base64);
                        blobCache.add(blobInfo);

                        // call the callback and populate the Title field with the file name
                        callback(blobInfo.blobUri(), { title: file.name });
                    };
                    reader.readAsDataURL(file);*/
              }
            }
          }
          const conf = Object.assign(defaultConfig, this.editorConfig)
          window.tinymce
            .init(conf)
            .then((editors) => {
              this.editor = editors[0]
              this.setContent(this.content || '')
              this.setMode(this.mode)
              this.show()
            })
            .catch((error) => {
              this.$message.error('富文本编辑器初始化失败: ' + error)
            })
        }
      )
    },
    /**
     * 上传文件至服务器
     * @param {string} metaType 上传类型：image 图片 ，video 视频 , file 文件
     * @param {file} file 需上传的文件
     * @param {Function} success 上传成功的回调
     * @param {Function} failure 上传失败的回调
    */
    uploadToServer(metaType, file, success, failure) {
      if (metaType === 'image') {
        // 若为上传图片，限制上传大小
        const isLt2M = file.size / 1024 / 1024 <= this.imageSize
        if (!isLt2M) {
          this.$notify.error({
            title: '错误',
            offset: 100,
            message: `上传图片大小不能超过 ${this.imageSize}MB!`
          })
          return false
        }
      }
      const flag = this.beforeUpload(metaType, file)
      if (!flag) {
        return false
      }

      const formData = new FormData() // 创建form表单
      formData.append('file', file)
      for (const key in this.uploadData) { // 添加其他form表单数据
        formData.append(key, this.uploadData[key])
      }
      const loading = this.$loading({
        lock: true,
        text: '正在上传...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      })
      request.post(this.uploadUrl, formData).then((result) => {
        const url = (result.data && result.data[this.uploadResField]) || ''
        if (!url) {
          this.$message.error({
            message: `上传结果异常!`,
            duration: 3000
          })
          return
        }
        success(url)
        loading.close()
      }).catch((error) => {
        failure('Error: ' + error)
        this.$message.error('上传失败')
        loading.close()
      }).finally((ff) => {
        loading.close()
      })
    },

    /**
     * 插入内容
     * @param {string} content 内容/文件地址
     * @param {string} type 内容类型： image 图片, video 视频，audio 音频
     * */
    insertContent(content, type) {
      /* // 光标放最后?
      this.editor.execCommand('selectAll')
      this.editor.selection.getRng().collapse(false)
      this.editor.focus() */

      let htmlContent = content
      if (type === 'image') {
        htmlContent = `<img src="${content}" style="max-width:100%;"/>`
      } else if (type === 'video') {
        htmlContent = `<video src="${content}" 
            webkit-playsinline="true" 
            x5-video-player-type="h5" 
            x5-video-orientation="portraint" 
            controls="controls" style="max-width:100%"></video><p><br></p>`
      } else if (type === 'audio') {
        htmlContent = `<audio src="${content}" controls="controls" style="max-width:100%"></audio><p><br></p>`
      }
      this.editor.insertContent(htmlContent)
    },
    /**
     * 获取编辑器中的html代码
     * @return {*}
     */
    getContent() {
      return this.editor.getContent()
    },
    /**
     * 获取编辑器中的纯文本
     * @return {*}
     */
    getText() {
      return this.editor.getContent({ format: 'text' })
    },
    /**
     * 保存并获取编辑器中的html代码
     * @return {*}
     */
    save() {
      return this.editor.save()
    },
    /**
     * 设置富文本编辑器展示的内容
     * @param content
     */
    setContent(content) {
      if (this.editor) {
        this.editor.setContent(content)
      } else {
        console.warn('编辑器尚未初始化')
      }
    },
    /**
     * 设置富文本编辑器模式
     * @param mode 模式值，readonly(只读)/design(编辑)
     */
    setMode(mode) {
      if (this.editor) {
        this.editor.setMode(mode)
      } else {
        console.warn('编辑器尚未初始化')
      }
    },
    // 光标放最后
    goEnd() {
      const editor = this.editor
      editor.execCommand('selectAll')
      editor.selection.getRng().collapse(false)
      editor.focus()
    },
    // 显示编辑器
    show() {
      this.editor.show()
    },
    // 隐藏编辑器
    hide() {
      this.editor.hide()
    },
    // 清空内容
    clear() {
      console.log(this.editor)
      this.editor.setContent('')
    }
  },
  render(h) {
    return h(this.editorConfig.inline ? 'div' : 'textarea', {
      attrs: {
        id: this.editorId
      },
      style: {
        // display: 'none',
        visibility: 'hidden'
      }
    })
  }
}
</script>
